//****************************************************************************
// *Spine Runtimes License Agreement
// * Last updated January 1, 2020. Replaces all prior versions.
// *
// * Copyright (c) 2013 - 2020, Esoteric Software LLC
// *
// * Integration of the Spine Runtimes into software or otherwise creating
// * derivative works of the Spine Runtimes is permitted under the terms and
// * conditions of Section 2 of the Spine Editor License Agreement:
// *http://esotericsoftware.com/spine-editor-license
// *
// *Otherwise, it is permitted to integrate the Spine Runtimes into software
// * or otherwise create derivative works of the Spine Runtimes (collectively,
// * "Products"), provided that each user of the Products must obtain their own
// * Spine Editor license and redistribution of the Products in any form must
// * include this license and copyright notice.
// *
// * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
// * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
// * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
// * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
// * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ***************************************************************************

#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
#define NEW_PREFAB_SYSTEM
#endif

#define SPINE_SKELETONMECANIM

using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Spine.Unity.Editor
{

    /// <summary>
    /// [SUPPORTS]
    /// Linear, Constant, and Bezier Curves*
    /// Inverse Kinematics*
    /// Inherit Rotation
    /// Translate Timeline
    /// Rotate Timeline
    /// Scale Timeline**
    /// Event Timeline***
    /// Attachment Timeline
    ///
    /// RegionAttachment
    /// MeshAttachment (optionally Skinned)
    ///
    /// [LIMITATIONS]
    /// *Bezier Curves are baked into the animation at 60fps and are not realtime. Use bakeIncrement constant to adjust key density if desired.
    /// *Inverse Kinematics is baked into the animation at 60fps and are not realtime. Use bakeIncrement constant to adjust key density if desired.
    /// ***Events may only fire 1 type of data per event in Unity safely so priority to String data if present in Spine key, otherwise a Float is sent whether the Spine key was Int or Float with priority given to Int.
    ///
    /// [DOES NOT SUPPORT]
    /// FFD (Unity does not provide access to BlendShapes with code)
    /// Color Keys (Maybe one day when Unity supports full FBX standard and provides access with code)
    /// Draw Order Keyframes
    /// </summary>
    public static class SkeletonBaker
    {

        #region SkeletonMecanim's Mecanim Clips
#if SPINE_SKELETONMECANIM
        public static void UpdateMecanimClips(SkeletonDataAsset skeletonDataAsset)
        {
            if (skeletonDataAsset.controller == null)
                return;

            SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
        }

        public static void GenerateMecanimAnimationClips(SkeletonDataAsset skeletonDataAsset)
        {
            var data = skeletonDataAsset.GetSkeletonData(true);
            if (data == null)
            {
                Debug.LogError("SkeletonData loading failed!", skeletonDataAsset);
                return;
            }

            string dataPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
            string controllerPath = dataPath.Replace(AssetUtility.SkeletonDataSuffix, "_Controller").Replace(".asset", ".controller");
            UnityEditor.Animations.AnimatorController controller;
            if (skeletonDataAsset.controller != null)
            {
                controller = (UnityEditor.Animations.AnimatorController)skeletonDataAsset.controller;
                controllerPath = AssetDatabase.GetAssetPath(controller);
            }
            else
            {
                if (File.Exists(controllerPath))
                {
                    if (EditorUtility.DisplayDialog("Controller Overwrite Warning", "Unknown Controller already exists at: " + controllerPath, "Update", "Overwrite"))
                    {
                        controller = (UnityEditor.Animations.AnimatorController)AssetDatabase.LoadAssetAtPath(controllerPath, typeof(RuntimeAnimatorController));
                    }
                    else
                    {
                        controller = (UnityEditor.Animations.AnimatorController)UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
                    }
                }
                else
                {
                    controller = (UnityEditor.Animations.AnimatorController)UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
                }

            }

            skeletonDataAsset.controller = controller;
            EditorUtility.SetDirty(skeletonDataAsset);

            UnityEngine.Object[] objs = AssetDatabase.LoadAllAssetsAtPath(controllerPath);

            var unityAnimationClipTable = new Dictionary<string, AnimationClip>();
            var spineAnimationTable = new Dictionary<string, Spine.Animation>();

            foreach (var o in objs)
            {
                //Debug.LogFormat("({0}){1} : {3} + {2} + {4}", o.GetType(), o.name, o.hideFlags, o.GetInstanceID(), o.GetHashCode());
                // There is a bug in Unity 5.3.3 (and likely before) that creates
                // a duplicate AnimationClip when you duplicate a Mecanim Animator State.
                // These duplicates seem to be identifiable by their HideFlags, so we'll exclude them.
                if (o is AnimationClip)
                {
                    var clip = o as AnimationClip;
                    if (!clip.HasFlag(HideFlags.HideInHierarchy))
                    {
                        if (unityAnimationClipTable.ContainsKey(clip.name))
                        {
                            Debug.LogWarningFormat("Duplicate AnimationClips were found named {0}", clip.name);
                        }
                        unityAnimationClipTable.Add(clip.name, clip);
                    }
                }
            }

            foreach (var animations in data.Animations)
            {
                string animationName = animations.Name; // Review for unsafe names. Requires runtime implementation too.
                spineAnimationTable.Add(animationName, animations);

                if (unityAnimationClipTable.ContainsKey(animationName) == false)
                {
                    AnimationClip newClip = new AnimationClip
                    {
                        name = animationName
                    };
                    //AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(dataPath) + "/" + animationName + ".asset");
                    AssetDatabase.AddObjectToAsset(newClip, controller);
                    unityAnimationClipTable.Add(animationName, newClip);
                }

                AnimationClip clip = unityAnimationClipTable[animationName];
                clip.SetCurve("", typeof(GameObject), "dummy", AnimationCurve.Linear(0, 0, animations.Duration, 0));
                var settings = AnimationUtility.GetAnimationClipSettings(clip);
                settings.stopTime = animations.Duration;
                SetAnimationSettings(clip, settings);

                AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]);
                foreach (Timeline t in animations.Timelines)
                {
                    if (t is EventTimeline)
                        ParseEventTimeline((EventTimeline)t, clip, SendMessageOptions.DontRequireReceiver);
                }

                EditorUtility.SetDirty(clip);
                unityAnimationClipTable.Remove(animationName);
            }

            foreach (var clip in unityAnimationClipTable.Values)
            {
                AnimationClip.DestroyImmediate(clip, true);
            }

            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();
        }

        static bool HasFlag(this UnityEngine.Object o, HideFlags flagToCheck)
        {
            return (o.hideFlags & flagToCheck) == flagToCheck;
        }
#endif
        #endregion

        #region Prefab and AnimationClip Baking
        /// <summary>
        /// Interval between key sampling for Bezier curves, IK controlled bones, and Inherit Rotation effected bones.
        /// </summary>
        const float BakeIncrement = 1 / 60f;

        public static void BakeToPrefab(SkeletonDataAsset skeletonDataAsset, ExposedList<Skin> skins, string outputPath = "", bool bakeAnimations = true, bool bakeIK = true, SendMessageOptions eventOptions = SendMessageOptions.DontRequireReceiver)
        {
            if (skeletonDataAsset == null || skeletonDataAsset.GetSkeletonData(true) == null)
            {
                Debug.LogError("Could not export Spine Skeleton because SkeletonDataAsset is null or invalid!");
                return;
            }

            if (outputPath == "")
            {
                outputPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(skeletonDataAsset)).Replace('\\', '/') + "/Baked";
                System.IO.Directory.CreateDirectory(outputPath);
            }
            var skeletonData = skeletonDataAsset.GetSkeletonData(true);
            bool hasAnimations = bakeAnimations && skeletonData.Animations.Count > 0;
            UnityEditor.Animations.AnimatorController controller = null;
            Dictionary<UnityEditor.Animations.AnimatorState, Dictionary<string, List<(Vector2, List<Vector3>)>>> deform_datas = new Dictionary<UnityEditor.Animations.AnimatorState, Dictionary<string, List<(Vector2, List<Vector3>)>>>();



            foreach (var skin in skins)
            {
                bool newPrefab = false;

                string prefabPath = outputPath + "/" + skeletonDataAsset.skeletonJSON.name + " (" + skin.Name + ").prefab";

                Object prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));

                if (prefab == null)
                {
#if NEW_PREFAB_SYSTEM
                    GameObject emptyGameObject = new GameObject();
                    prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(emptyGameObject, prefabPath, InteractionMode.AutomatedAction);
                    GameObject.DestroyImmediate(emptyGameObject);
#else
					prefab = PrefabUtility.CreateEmptyPrefab(prefabPath);
#endif
                    newPrefab = true;
                }

                Dictionary<string, Mesh> meshTable = new Dictionary<string, Mesh>();
                List<string> unusedMeshNames = new List<string>();
                Object[] assets = AssetDatabase.LoadAllAssetsAtPath(prefabPath);
                foreach (var obj in assets)
                {
                    if (obj is Mesh)
                    {
                        meshTable.Add(obj.name, (Mesh)obj);
                        unusedMeshNames.Add(obj.name);
                    }
                }

                GameObject prefabRoot = EditorInstantiation.NewGameObject("root", true);

                Dictionary<string, Transform> slotTable = new Dictionary<string, Transform>();
                List<Color> slotColor = new List<Color>(0); ;
                Dictionary<string, Transform> boneTable = new Dictionary<string, Transform>();
                Dictionary<string, Transform> attachmentsTable = new Dictionary<string, Transform>();

                //create bones
                for (int i = 0; i < skeletonData.Bones.Count; i++)
                {
                    var boneData = skeletonData.Bones.Items[i];
                    Transform boneTransform = EditorInstantiation.NewGameObject(boneData.Name, true).transform;
                    boneTransform.parent = prefabRoot.transform;
                    boneTable.Add(boneTransform.name, boneTransform);
                }
                List<Transform> boneList = boneTable.Values.ToList();

                for (int i = 0; i < skeletonData.Bones.Count; i++)
                {

                    var boneData = skeletonData.Bones.Items[i];
                    Transform boneTransform = boneTable[boneData.Name];
                    Transform parentTransform = null;
                    if (i > 0)
                        parentTransform = boneTable[boneData.Parent.Name];
                    else
                        parentTransform = boneTransform.parent;

                    boneTransform.parent = parentTransform;
                    boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0);
                    var tm = boneData.TransformMode;
                    if (tm.InheritsRotation())
                        boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation);
                    else
                        boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation);

                    if (tm.InheritsScale())
                        boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1);
                }
                //create slots and attachments
                for (int slotIndex = 0; slotIndex < skeletonData.Slots.Count; slotIndex++)
                {
                    var slotData = skeletonData.Slots.Items[slotIndex];
                    slotColor.Add(new Color(slotData.R, slotData.G, slotData.B, slotData.A));
                    Transform slotTransform = EditorInstantiation.NewGameObject(slotData.Name, true).transform;
                    slotTransform.parent = prefabRoot.transform;
                    slotTable.Add(slotData.Name, slotTransform);
                    var skinEntries = new List<Skin.SkinEntry>();

                    skin.GetAttachments(slotIndex, skinEntries);
                    (skeletonData.DefaultSkin ?? skin).GetAttachments(slotIndex, skinEntries);
                    for (int a = 0; a < skinEntries.Count; a++)
                    {
                        var attachment = skinEntries[a].Attachment;
                        string attachmentName = skinEntries[a].Attachment.Name.Replace("/", "^");
                        Debug.Log(attachment.Name);
                        string attachmentMeshName = "[" + slotData.Name + "] " + attachmentName;
                        Vector3 offset = Vector3.zero;
                        float rotation = 0;
                        Mesh mesh = null;
                        Material material = null;
                        bool isWeightedMesh = false;

                        if (meshTable.ContainsKey(attachmentMeshName))
                            mesh = meshTable[attachmentMeshName];
                        if (attachment is RegionAttachment)
                        {
                            var regionAttachment = (RegionAttachment)attachment;
                            offset.x = regionAttachment.X;
                            offset.y = regionAttachment.Y;
                            rotation = regionAttachment.Rotation;
                            Transform bone = boneTable[slotData.BoneData.Name];
                            mesh = ExtractRegionAttachment(attachmentMeshName, regionAttachment, boneList, boneList.IndexOf(bone), mesh);
                            material = attachment.GetMaterial();
                            unusedMeshNames.Remove(attachmentMeshName);
                            if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
                                AssetDatabase.AddObjectToAsset(mesh, prefab);
                            Transform transform = bone;
                            Vector3 scale = new Vector3(1, 1, 1);
                            Vector3 position = transform.position;
                            Quaternion rotate = transform.rotation;
                            while (transform.parent != null)
                            {
                                scale = Vector3.Scale(scale, transform.localScale);
                                transform = transform.parent;
                            }

                            Vector3[] vert = mesh.vertices;
                            for (int i = 0; i < vert.Length; i++)
                            {
                                vert[i] = rotate * Vector3.Scale(vert[i], scale) + position;
                            }
                            mesh.vertices = vert;
                            mesh.RecalculateBounds();
                            mesh.RecalculateNormals();
                        }
                        else if (attachment is MeshAttachment)
                        {
                            var meshAttachment = (MeshAttachment)attachment;
                            isWeightedMesh = (meshAttachment.Bones != null);
                            offset.x = 0;
                            offset.y = 0; ;
                            rotation = 0;

                            if (isWeightedMesh)
                            {
                                mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, slotIndex, skeletonData, boneList, mesh);
                            }
                            else
                            {
                                Transform bone = boneTable[slotData.BoneData.Name];

                                mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, boneList, boneList.IndexOf(bone), mesh);
                                Transform transform = bone;
                                Vector3 scale = Vector3.one;
                                Vector3 position = transform.position;
                                Quaternion rotate = transform.rotation;
                                while (transform.parent != null)
                                {
                                    scale = Vector3.Scale(scale, transform.localScale);
                                    transform = transform.parent;
                                }

                                Vector3[] vert = mesh.vertices;
                                for (int i = 0; i < vert.Length; i++)
                                {
                                    vert[i] = rotate * Vector3.Scale(vert[i], scale) + position;
                                }
                                mesh.vertices = vert;
                                mesh.RecalculateBounds();
                                mesh.RecalculateNormals();
                            };
                            material = attachment.GetMaterial();
                            unusedMeshNames.Remove(attachmentMeshName);
                            if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
                                AssetDatabase.AddObjectToAsset(mesh, prefab);
                        }
                        else
                            continue;

                        Transform attachmentTransform = EditorInstantiation.NewGameObject(attachmentName, true).transform;
                        attachmentsTable[attachmentName] = attachmentTransform;
                        attachmentTransform.parent = slotTransform;
                        attachmentTransform.localPosition = offset;
                        attachmentTransform.localRotation = Quaternion.Euler(0, 0, rotation);
                        if (isWeightedMesh)
                        {
                            attachmentTransform.position = Vector3.zero;
                            attachmentTransform.rotation = Quaternion.identity;
                            var skinnedMeshRenderer = attachmentTransform.gameObject.AddComponent<SkinnedMeshRenderer>();
                            skinnedMeshRenderer.rootBone = boneList[0];
                            skinnedMeshRenderer.bones = boneList.ToArray();
                            skinnedMeshRenderer.sharedMesh = mesh;
                        }
                        else
                        {
                            var skinnedMeshRenderer = attachmentTransform.gameObject.AddComponent<SkinnedMeshRenderer>();
                            skinnedMeshRenderer.rootBone = boneList[0];
                            Transform[] bones = new Transform[1] { attachmentTransform };
                            skinnedMeshRenderer.bones = boneList.ToArray();
                            skinnedMeshRenderer.sharedMesh = mesh;
                        }
                        var materialPath = AssetDatabase.GetAssetPath(material);
                        string newpath; Material existingMaterial;
                        switch (slotData.BlendMode)
                        {
                            case BlendMode.Normal:
                                material.shader = Shader.Find("Spine/Skeleton Tint");
                                break;
                            case BlendMode.Additive:
                                newpath = materialPath.Insert(materialPath.LastIndexOf("."), "_Add");
                                existingMaterial = AssetDatabase.LoadAssetAtPath<Material>(newpath);
                                if (existingMaterial == null)
                                {
                                    existingMaterial = new Material(material);
                                    existingMaterial.shader = Shader.Find("Spine/Blend Modes/Skeleton PMA Additive");
                                    AssetDatabase.CreateAsset(existingMaterial, newpath);
                                    AssetDatabase.SaveAssets();
                                }
                                material = existingMaterial;
                                break;
                            case BlendMode.Multiply:
                                newpath = materialPath.Insert(materialPath.LastIndexOf("."), "_Mul");
                                existingMaterial = AssetDatabase.LoadAssetAtPath<Material>(newpath);
                                if (existingMaterial == null)
                                {
                                    existingMaterial = new Material(material);
                                    existingMaterial.shader = Shader.Find("Spine/Blend Modes/Skeleton PMA Multiply");
                                    AssetDatabase.CreateAsset(existingMaterial, newpath);
                                    AssetDatabase.SaveAssets();
                                }
                                material = existingMaterial;
                                break;
                            case BlendMode.Screen:
                                newpath = materialPath.Insert(materialPath.LastIndexOf("."), "_Scr");
                                existingMaterial = AssetDatabase.LoadAssetAtPath<Material>(newpath);
                                if (existingMaterial == null)
                                {
                                    existingMaterial = new Material(material);
                                    existingMaterial.shader = Shader.Find("Spine/Blend Modes/Skeleton PMA Screen");
                                    AssetDatabase.CreateAsset(existingMaterial, newpath);
                                    AssetDatabase.SaveAssets();
                                }
                                material = existingMaterial;
                                break;
                            default:
                                break;
                        }
                        attachmentTransform.GetComponent<Renderer>().sharedMaterial = material;
                        attachmentTransform.GetComponent<Renderer>().sortingOrder = slotIndex;

                        if (slotData.AttachmentName == null || skinEntries[a].Name != slotData.AttachmentName.Replace("/", "^"))
                            attachmentTransform.gameObject.SetActive(false);

                    }

                }

                foreach (var slotData in skeletonData.Slots)
                {
                    Transform slotTransform = slotTable[slotData.Name];
                    slotTransform.parent = boneTable[slotData.BoneData.Name];

                    slotTransform.localPosition = Vector3.zero;
                    slotTransform.localRotation = Quaternion.identity;
                    slotTransform.localScale = Vector3.one;


                }

                if (hasAnimations)
                {
                    string controllerPath = outputPath + "/" + skeletonDataAsset.skeletonJSON.name + " Controller.controller";
                    bool newAnimContainer = false;

                    var runtimeController = AssetDatabase.LoadAssetAtPath(controllerPath, typeof(RuntimeAnimatorController));

                    if (runtimeController != null)
                    {
                        controller = (UnityEditor.Animations.AnimatorController)runtimeController;
                        //controller = new AnimatorController();
                    }
                    else
                    {
                        controller = UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
                        newAnimContainer = true;
                    }

                    var existingClipTable = new Dictionary<string, AnimationClip>();
                    var unusedClipNames = new List<string>();
                    Object[] animObjs = AssetDatabase.LoadAllAssetsAtPath(controllerPath);

                    foreach (Object o in animObjs)
                    {
                        if (o is AnimationClip)
                        {
                            var clip = (AnimationClip)o;
                            existingClipTable.Add(clip.name, clip);
                            unusedClipNames.Add(clip.name);
                        }
                    }

                    Dictionary<int, List<string>> slotLookup = new Dictionary<int, List<string>>();

                    int skinCount = skins.Count;

                    for (int s = 0; s < skeletonData.Slots.Count; s++)
                    {

                        List<string> attachmentNames = new List<string>();
                        for (int i = 0; i < skinCount; i++)
                        {
                            var skin0 = skins.Items[i];
                            var skinEntries = new List<Skin.SkinEntry>();
                            skin0.GetAttachments(s, skinEntries);
                            foreach (var entry in skinEntries)
                            {
                                if (!attachmentNames.Contains(entry.Name))
                                    attachmentNames.Add(entry.Name);
                            }
                        }
                        slotLookup.Add(s, attachmentNames);
                    }

                    foreach (var anim in skeletonData.Animations)
                    {

                        AnimationClip clip = null;
                        Dictionary<string, List<(Vector2, List<Vector3>)>> deform_data;
                        if (existingClipTable.ContainsKey(anim.Name))
                        {
                            clip = existingClipTable[anim.Name];
                        }
                        (deform_data, clip) = ExtractAnimation(anim.Name, skeletonData, slotLookup, bakeIK, eventOptions, clip);

                        if (unusedClipNames.Contains(clip.name))
                        {
                            unusedClipNames.Remove(clip.name);
                        }
                        else
                        {
                            AssetDatabase.AddObjectToAsset(clip, controller);
                            var state = controller.AddMotion(clip);
                            if (deform_data != null)
                            {
                                deform_datas[state] = deform_data;
                            }

                        }
                    }


                    foreach (var item in deform_datas)
                    {
                        var deformer = controller.AddEffectiveStateMachineBehaviour<Deformer>(item.Key, 0);
                        deformer.timeline = new List<deformData>();
                        foreach (var item1 in item.Value)
                        {
                            var deform_data = new deformData()
                            {
                                attachment_path = item1.Key,
                                deformTimeDatas = new List<deformTimeData>()
                            };
                            foreach (var item2 in item1.Value)
                            {
                                deform_data.deformTimeDatas.Add(new deformTimeData()
                                {
                                    time = item2.Item1.x,
                                    curve_type = item2.Item1.y,
                                    pos = item2.Item2
                                });
                            }
                            deformer.timeline.Add(deform_data);
                        }
                    }
                    if (newAnimContainer)
                    {
                        EditorUtility.SetDirty(controller);
                        AssetDatabase.SaveAssets();
                        AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate);
                        AssetDatabase.Refresh();
                    }
                    else
                    {

                        foreach (string str in unusedClipNames)
                        {
                            AnimationClip.DestroyImmediate(existingClipTable[str], true);
                        }

                        EditorUtility.SetDirty(controller);
                        AssetDatabase.SaveAssets();
                        AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate);
                        AssetDatabase.Refresh();
                    }

                    var animator = prefabRoot.AddComponent<Animator>();
                    animator.applyRootMotion = false;
                    animator.runtimeAnimatorController = (RuntimeAnimatorController)controller;
                    EditorGUIUtility.PingObject(controller);
                }
                setSetupPoseClip(ref controller, boneList, slotTable, skeletonData.Slots.Items.ToList());

                if (newPrefab)
                {
#if NEW_PREFAB_SYSTEM
                    PrefabUtility.SaveAsPrefabAssetAndConnect(prefabRoot, prefabPath, InteractionMode.AutomatedAction);
#else
					PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ConnectToPrefab);
#endif
                }
                else
                {

                    foreach (string str in unusedMeshNames)
                    {
                        Mesh.DestroyImmediate(meshTable[str], true);
                    }

#if NEW_PREFAB_SYSTEM
                    PrefabUtility.SaveAsPrefabAssetAndConnect(prefabRoot, prefabPath, InteractionMode.AutomatedAction);
#else
					PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ReplaceNameBased);
#endif
                }


                EditorGUIUtility.PingObject(prefab);

                AssetDatabase.Refresh();
                AssetDatabase.SaveAssets();

                GameObject.DestroyImmediate(prefabRoot);

            }
        }

        #region Attachment Baking
        static Bone DummyBone;
        static Slot DummySlot;

        internal static Bone GetDummyBone()
        {
            if (DummyBone != null)
                return DummyBone;

            SkeletonData skelData = new SkeletonData();
            BoneData data = new BoneData(0, "temp", null)
            {
                ScaleX = 1,
                ScaleY = 1,
                Length = 100
            };

            skelData.Bones.Add(data);

            Skeleton skeleton = new Skeleton(skelData);

            Bone bone = new Bone(data, skeleton, null);
            bone.UpdateWorldTransform();

            DummyBone = bone;

            return DummyBone;
        }

        internal static Slot GetDummySlot()
        {
            if (DummySlot != null)
                return DummySlot;

            Bone bone = GetDummyBone();

            SlotData data = new SlotData(0, "temp", bone.Data);
            Slot slot = new Slot(data, bone);
            DummySlot = slot;
            return DummySlot;
        }

        internal static Mesh ExtractRegionAttachment(string name, RegionAttachment attachment, List<Transform> boneList, int boneIndex, Mesh mesh = null, bool centered = true)
        {
            var bone = GetDummyBone();

            if (centered)
            {
                bone.X = 0;
                bone.Y = 0;
            }

            bone.UpdateWorldTransform();

            Vector2[] uvs = ExtractUV(attachment.UVs);
            float[] floatVerts = new float[8];
            attachment.ComputeWorldVertices(bone, floatVerts, 0);

            Vector3[] verts = ExtractVerts(floatVerts);
            //unrotate verts now that they're centered
            if (centered)
            {
                for (int i = 0; i < verts.Length; i++)
                    verts[i] = Quaternion.Euler(0, 0, 0) * verts[i];
            }

            int[] triangles = { 1, 3, 0, 2, 3, 1 };
            Color color = attachment.GetColor();

            if (mesh == null)
                mesh = new Mesh();

            mesh.triangles = new int[0];

            mesh.vertices = verts;
            mesh.uv = uvs;
            mesh.triangles = triangles;
            mesh.colors = new[] { color, color, color, color };
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();
            mesh.name = name;

            BoneWeight[] boneWeights = new BoneWeight[verts.Length];

            for (int i = 0; i < boneWeights.Length; i++)
            {
                var bw = new BoneWeight();
                bw.boneIndex0 = boneIndex;
                bw.weight0 = 1f;
                boneWeights[i] = bw;
            }

            Matrix4x4[] bindPoses = new Matrix4x4[boneList.Count];
            for (int i = 0; i < boneList.Count; i++)
            {
                bindPoses[i] = boneList[i].worldToLocalMatrix;
            }

            mesh.boneWeights = boneWeights;
            mesh.bindposes = bindPoses;

            return mesh;
        }

        internal static Mesh ExtractMeshAttachment(string name, MeshAttachment attachment, List<Transform> boneList, int boneIndex, Mesh mesh = null)
        {
            var slot = GetDummySlot();

            slot.Bone.X = 0;
            slot.Bone.Y = 0;
            slot.Bone.UpdateWorldTransform();

            Vector2[] uvs = ExtractUV(attachment.UVs);
            float[] floatVerts = new float[attachment.WorldVerticesLength];
            attachment.ComputeWorldVertices(slot, floatVerts);
            Vector3[] verts = ExtractVerts(floatVerts);

            int[] triangles = attachment.Triangles;
            Color color = attachment.GetColor();

            if (mesh == null)
                mesh = new Mesh();

            mesh.triangles = new int[0];

            mesh.vertices = verts;
            mesh.uv = uvs;
            mesh.triangles = triangles;
            Color[] colors = new Color[verts.Length];
            for (int i = 0; i < verts.Length; i++)
                colors[i] = color;

            mesh.colors = colors;
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();
            mesh.name = name;

            BoneWeight[] boneWeights = new BoneWeight[verts.Length];

            for (int i = 0; i < boneWeights.Length; i++)
            {
                var bw = new BoneWeight();
                bw.boneIndex0 = boneIndex;
                bw.weight0 = 1f;
                boneWeights[i] = bw;
            }

            Matrix4x4[] bindPoses = new Matrix4x4[boneList.Count];
            for (int i = 0; i < boneList.Count; i++)
            {
                bindPoses[i] = boneList[i].worldToLocalMatrix;
            }

            mesh.boneWeights = boneWeights;
            mesh.bindposes = bindPoses;

            return mesh;
        }

        public class BoneWeightContainer
        {
            public struct Pair
            {
                public Transform bone;
                public float weight;

                public Pair(Transform bone, float weight)
                {
                    this.bone = bone;
                    this.weight = weight;
                }
            }

            public List<Transform> bones;
            public List<float> weights;
            public List<Pair> pairs;


            public BoneWeightContainer()
            {
                this.bones = new List<Transform>();
                this.weights = new List<float>();
                this.pairs = new List<Pair>();
            }

            public void Add(Transform transform, float weight)
            {
                bones.Add(transform);
                weights.Add(weight);

                pairs.Add(new Pair(transform, weight));
            }
        }

        internal static Mesh ExtractWeightedMeshAttachment(string name, MeshAttachment attachment, int slotIndex, SkeletonData skeletonData, List<Transform> boneList, Mesh mesh = null)
        {
            if (!attachment.IsWeighted())
                throw new System.ArgumentException("Mesh is not weighted.", "attachment");

            Skeleton skeleton = new Skeleton(skeletonData);
            skeleton.UpdateWorldTransform();

            float[] floatVerts = new float[attachment.WorldVerticesLength];
            attachment.ComputeWorldVertices(skeleton.Slots.Items[slotIndex], floatVerts);

            Vector2[] uvs = ExtractUV(attachment.UVs);
            Vector3[] verts = ExtractVerts(floatVerts);

            int[] triangles = attachment.Triangles;
            Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A);

            mesh = (mesh == null) ? new Mesh() : mesh;

            mesh.triangles = new int[0];

            mesh.vertices = verts;
            mesh.uv = uvs;
            mesh.triangles = triangles;
            Color[] colors = new Color[verts.Length];
            for (int i = 0; i < verts.Length; i++)
                colors[i] = color;

            mesh.colors = colors;
            mesh.name = name;
            mesh.RecalculateNormals();
            mesh.RecalculateBounds();

            // Handle weights and binding
            var weightTable = new Dictionary<int, BoneWeightContainer>();
            var warningBuilder = new System.Text.StringBuilder();

            int[] bones = attachment.Bones;
            float[] weights = attachment.Vertices;
            for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2)
            {

                int nn = bones[v++] + v;
                for (; v < nn; v++, b += 3)
                {
                    Transform boneTransform = boneList[bones[v]];
                    int vIndex = w / 2;
                    BoneWeightContainer container;
                    if (weightTable.ContainsKey(vIndex))
                        container = weightTable[vIndex];
                    else
                    {
                        container = new BoneWeightContainer();
                        weightTable.Add(vIndex, container);
                    }

                    float weight = weights[b + 2];
                    container.Add(boneTransform, weight);
                }
            }

            BoneWeight[] boneWeights = new BoneWeight[weightTable.Count];

            for (int i = 0; i < weightTable.Count; i++)
            {
                BoneWeight bw = new BoneWeight();
                var container = weightTable[i];

                var pairs = container.pairs.OrderByDescending(pair => pair.weight).ToList();
                float overpairs_weight = 0;
                for (int b = 0; b < pairs.Count; b++)
                {
                    if (b > 3)
                    {
                        if (warningBuilder.Length == 0)
                            warningBuilder.Insert(0, "[Weighted Mesh: " + name + "]\r\nUnity only supports 4 weight influences per vertex! The 4 strongest influences will be used.\r\n");

                        warningBuilder.AppendFormat("{0} ignored on vertex {1}!\r\n", pairs[b].bone.name, i);
                        overpairs_weight += pairs[b].weight;
                        continue;
                    }

                    int boneIndex = boneList.IndexOf(pairs[b].bone);
                    float weight = pairs[b].weight;

                    switch (b)
                    {
                        case 0:
                            bw.boneIndex0 = boneIndex;
                            bw.weight0 = weight;
                            break;
                        case 1:
                            bw.boneIndex1 = boneIndex;
                            bw.weight1 = weight;
                            break;
                        case 2:
                            bw.boneIndex2 = boneIndex;
                            bw.weight2 = weight;
                            break;
                        case 3:
                            bw.boneIndex3 = boneIndex;
                            bw.weight3 = weight;
                            break;
                    }
                }
                bw.weight0 += overpairs_weight;
                boneWeights[i] = bw;
            }

            Matrix4x4[] bindPoses = new Matrix4x4[boneList.Count];
            for (int i = 0; i < boneList.Count; i++)
            {
                bindPoses[i] = boneList[i].worldToLocalMatrix;
            }

            mesh.boneWeights = boneWeights;
            mesh.bindposes = bindPoses;

            string warningString = warningBuilder.ToString();
            if (warningString.Length > 0)
                Debug.LogWarning(warningString);


            return mesh;
        }

        internal static Vector2[] ExtractUV(float[] floats)
        {
            Vector2[] arr = new Vector2[floats.Length / 2];

            for (int i = 0; i < floats.Length; i += 2)
            {
                arr[i / 2] = new Vector2(floats[i], floats[i + 1]);
            }

            return arr;
        }

        internal static Vector3[] ExtractVerts(float[] floats)
        {
            Vector3[] arr = new Vector3[floats.Length / 2];

            for (int i = 0; i < floats.Length; i += 2)
            {
                arr[i / 2] = new Vector3(floats[i], floats[i + 1], 0);// *scale;
            }

            return arr;
        }
        #endregion

        #region Animation Baking
        private static float ss;
        static (Dictionary<string, List<(Vector2, List<Vector3>)>>, AnimationClip) ExtractAnimation(string name, SkeletonData skeletonData, Dictionary<int, List<string>> slotLookup, bool bakeIK, SendMessageOptions eventOptions, AnimationClip clip = null)
        {
            var animation = skeletonData.FindAnimation(name);

            var timelines = animation.Timelines;

            Dictionary<string, List<(Vector2, List<Vector3>)>> deformerData = null;

            if (clip == null)
            {
                clip = new AnimationClip();
            }
            else
            {
                clip.ClearCurves();
                AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]);
            }

            clip.name = name;

            Skeleton skeleton = new Skeleton(skeletonData);

            List<int> ignoreRotateTimelineIndexes = new List<int>();

            if (bakeIK)
            {
                foreach (IkConstraint i in skeleton.IkConstraints)
                {
                    foreach (Bone b in i.Bones)
                    {
                        int index = skeleton.FindBoneIndex(b.Data.Name);
                        ignoreRotateTimelineIndexes.Add(index);
                        BakeBoneConstraints(b, animation, clip);
                    }
                }
            }

            foreach (Bone b in skeleton.Bones)
            {
                if (!b.Data.TransformMode.InheritsRotation())
                {
                    int index = skeleton.FindBoneIndex(b.Data.Name);

                    if (ignoreRotateTimelineIndexes.Contains(index) == false)
                    {
                        ignoreRotateTimelineIndexes.Add(index);
                        BakeBoneConstraints(b, animation, clip);
                    }
                }
            }
            foreach (Timeline t in timelines)
            {

                skeleton.SetToSetupPose();
                if (t is ScaleTimeline)
                {
                    ParseScaleTimeline(skeleton, (ScaleTimeline)t, clip);
                }
                else if (t is ShearTimeline)
                {
                    ParseShearTimeline((ShearTimeline)t, clip);
                }
                else if (t is PathConstraintMixTimeline)
                {
                    Debug.Log((PathConstraintMixTimeline)t);
                }
                else if (t is PathConstraintPositionTimeline)
                {
                    Debug.Log((PathConstraintPositionTimeline)t);
                }
                else if (t is PathConstraintSpacingTimeline)
                {
                    Debug.Log((PathConstraintSpacingTimeline)t);
                }
                else if (t is TranslateTimeline)
                {
                    ParseTranslateTimeline(skeleton, (TranslateTimeline)t, clip);
                }
                else if (t is RotateTimeline)
                {
                    //bypass any rotation keys if they're going to get baked anyway to prevent localEulerAngles vs Baked collision
                    if (ignoreRotateTimelineIndexes.Contains(((RotateTimeline)t).BoneIndex) == false)
                        ParseRotateTimeline(skeleton, (RotateTimeline)t, clip);
                }
                else if (t is AttachmentTimeline)
                {
                    ParseAttachmentTimeline(skeleton, (AttachmentTimeline)t, slotLookup, clip);
                }
                else if (t is EventTimeline)
                {
                    ParseEventTimeline((EventTimeline)t, clip, eventOptions);
                }
                else if (t is ColorTimeline)
                {
                    ParseColorTimeline(skeleton, (ColorTimeline)t, slotLookup, clip);
                }
                else if (t is TwoColorTimeline)
                {
                    ParseTwoColorTimeline((TwoColorTimeline)t, clip);
                }
                else if (t is DrawOrderTimeline)
                {
                    ParseDrawOrderTimeline((DrawOrderTimeline)t, clip);
                }
                else if (t is DeformTimeline)
                {
                    if (deformerData == null) deformerData = new Dictionary<string, List<(Vector2, List<Vector3>)>>();
                    ParseDeformTimeline(skeleton, (DeformTimeline)t, ref deformerData, clip);
                }

            }
            var settings = AnimationUtility.GetAnimationClipSettings(clip);
            settings.loopTime = true;
            settings.stopTime = Mathf.Max(clip.length, 0.001f);

            SetAnimationSettings(clip, settings);

            clip.EnsureQuaternionContinuity();

            EditorUtility.SetDirty(clip);

            return (deformerData, clip);
        }

        static int BinarySearch(float[] values, float target)
        {
            int low = 0;
            int high = values.Length - 2;
            if (high == 0) return 1;
            int current = (int)((uint)high >> 1);
            while (true)
            {
                if (values[(current + 1)] <= target)
                    low = current + 1;
                else
                    high = current;

                if (low == high) return (low + 1);
                current = (int)((uint)(low + high) >> 1);
            }
        }

        static void BakeBoneConstraints(Bone bone, Spine.Animation animation, AnimationClip clip)
        {
            Skeleton skeleton = bone.Skeleton;
            bool inheritRotation = bone.Data.TransformMode.InheritsRotation();

            animation.Apply(skeleton, 0, 0, false, null, 1f, MixBlend.Setup, MixDirection.In);
            skeleton.UpdateWorldTransform();
            float duration = animation.Duration;

            AnimationCurve curve = new AnimationCurve();

            List<Keyframe> keys = new List<Keyframe>();

            float rotation = bone.AppliedRotation;
            if (!inheritRotation)
                rotation = GetUninheritedAppliedRotation(bone);

            keys.Add(new Keyframe(0, rotation, 0, 0));

            int listIndex = 1;

            float r = rotation;

            int steps = Mathf.CeilToInt(duration / BakeIncrement);

            float currentTime = 0;
            float angle = rotation;

            for (int i = 1; i <= steps; i++)
            {
                currentTime += BakeIncrement;
                if (i == steps)
                    currentTime = duration;

                animation.Apply(skeleton, 0, currentTime, true, null, 1f, MixBlend.Setup, MixDirection.In);
                skeleton.UpdateWorldTransform();

                int pIndex = listIndex - 1;

                Keyframe pk = keys[pIndex];

                pk = keys[pIndex];

                rotation = inheritRotation ? bone.AppliedRotation : GetUninheritedAppliedRotation(bone);

                angle += Mathf.DeltaAngle(angle, rotation);

                r = angle;

                float rOut = (r - pk.value) / (currentTime - pk.time);

                pk.outTangent = rOut;

                keys.Add(new Keyframe(currentTime, r, rOut, 0));

                keys[pIndex] = pk;

                listIndex++;
            }

            curve = EnsureCurveKeyCount(new AnimationCurve(keys.ToArray()));

            string path = GetPath(bone.Data);
            string propertyName = "localEulerAnglesBaked";

            EditorCurveBinding xBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".x");
            AnimationUtility.SetEditorCurve(clip, xBind, new AnimationCurve());
            EditorCurveBinding yBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".y");
            AnimationUtility.SetEditorCurve(clip, yBind, new AnimationCurve());
            EditorCurveBinding zBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".z");
            AnimationUtility.SetEditorCurve(clip, zBind, curve);
        }

        static void ParseTranslateTimeline(Skeleton skeleton, TranslateTimeline timeline, AnimationClip clip)
        {
            var boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
            var bone = skeleton.Bones.Items[timeline.BoneIndex];

            AnimationCurve xCurve = new AnimationCurve();
            AnimationCurve yCurve = new AnimationCurve();
            AnimationCurve zCurve = new AnimationCurve();

            float endTime = timeline.Frames[(timeline.FrameCount * 3) - 3];

            float currentTime = timeline.Frames[0];

            List<Keyframe> xKeys = new List<Keyframe>();
            List<Keyframe> yKeys = new List<Keyframe>();

            xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + boneData.X, 0, 0));
            yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + boneData.Y, 0, 0));

            int listIndex = 1;
            int frameIndex = 1;
            int f = 3;
            float[] frames = timeline.Frames;
            skeleton.SetToSetupPose();
            float lastTime = 0;

            while (currentTime < endTime)
            {

                int pIndex = listIndex - 1;

                float curveType = timeline.GetCurveType(frameIndex - 1);
                if (curveType == 0)
                {
                    //linear
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];

                    float time = frames[f];
                    float x = frames[f + 1] + boneData.X;
                    float y = frames[f + 2] + boneData.Y;

                    float xOut = (x - px.value) / (time - px.time);
                    float yOut = (y - py.value) / (time - py.time);

                    px.outTangent = xOut;
                    py.outTangent = yOut;

                    xKeys.Add(new Keyframe(time, x, xOut, 0));
                    yKeys.Add(new Keyframe(time, y, yOut, 0));

                    xKeys[pIndex] = px;
                    yKeys[pIndex] = py;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 1)
                {
                    //stepped
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];

                    float time = frames[f];
                    float x = frames[f + 1] + boneData.X;
                    float y = frames[f + 2] + boneData.Y;

                    float xOut = float.PositiveInfinity;
                    float yOut = float.PositiveInfinity;

                    px.outTangent = xOut;
                    py.outTangent = yOut;

                    xKeys.Add(new Keyframe(time, x, xOut, 0));
                    yKeys.Add(new Keyframe(time, y, yOut, 0));

                    xKeys[pIndex] = px;
                    yKeys[pIndex] = py;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 2)
                {

                    //bezier
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];
                    float time = frames[f];

                    int steps = Mathf.FloorToInt((time - px.time) / BakeIncrement);

                    for (int i = 1; i <= steps; i++)
                    {
                        currentTime += BakeIncrement;
                        if (i == steps)
                            currentTime = time;

                        timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                        px = xKeys[listIndex - 1];
                        py = yKeys[listIndex - 1];

                        float xOut = (bone.X - px.value) / (currentTime - px.time);
                        float yOut = (bone.Y - py.value) / (currentTime - py.time);

                        px.outTangent = xOut;
                        py.outTangent = yOut;

                        xKeys.Add(new Keyframe(currentTime, bone.X, xOut, 0));
                        yKeys.Add(new Keyframe(currentTime, bone.Y, yOut, 0));

                        xKeys[listIndex - 1] = px;
                        yKeys[listIndex - 1] = py;

                        listIndex++;
                        lastTime = currentTime;
                    }
                }

                frameIndex++;
                f += 3;
            }

            xCurve = EnsureCurveKeyCount(new AnimationCurve(xKeys.ToArray()));
            yCurve = EnsureCurveKeyCount(new AnimationCurve(yKeys.ToArray()));



            string path = GetPath(boneData);
            const string propertyName = "localPosition";

            clip.SetCurve(path, typeof(Transform), propertyName + ".x", xCurve);
            clip.SetCurve(path, typeof(Transform), propertyName + ".y", yCurve);
            clip.SetCurve(path, typeof(Transform), propertyName + ".z", zCurve);
        }

        static void ParseScaleTimeline(Skeleton skeleton, ScaleTimeline timeline, AnimationClip clip)
        {
            var boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
            var bone = skeleton.Bones.Items[timeline.BoneIndex];

            AnimationCurve xCurve = new AnimationCurve();
            AnimationCurve yCurve = new AnimationCurve();
            AnimationCurve zCurve = new AnimationCurve();

            float endTime = timeline.Frames[(timeline.FrameCount * 3) - 3];

            float currentTime = timeline.Frames[0];

            List<Keyframe> xKeys = new List<Keyframe>();
            List<Keyframe> yKeys = new List<Keyframe>();

            xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * boneData.ScaleX, 0, 0));
            yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * boneData.ScaleY, 0, 0));

            int listIndex = 1;
            int frameIndex = 1;
            int f = 3;
            float[] frames = timeline.Frames;
            skeleton.SetToSetupPose();
            float lastTime = 0;
            while (currentTime < endTime)
            {
                int pIndex = listIndex - 1;
                float curveType = timeline.GetCurveType(frameIndex - 1);

                if (curveType == 0)
                {
                    //linear
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];

                    float time = frames[f];
                    float x = frames[f + 1] * boneData.ScaleX;
                    float y = frames[f + 2] * boneData.ScaleY;

                    float xOut = (x - px.value) / (time - px.time);
                    float yOut = (y - py.value) / (time - py.time);

                    px.outTangent = xOut;
                    py.outTangent = yOut;

                    xKeys.Add(new Keyframe(time, x, xOut, 0));
                    yKeys.Add(new Keyframe(time, y, yOut, 0));

                    xKeys[pIndex] = px;
                    yKeys[pIndex] = py;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 1)
                {
                    //stepped
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];

                    float time = frames[f];
                    float x = frames[f + 1] * boneData.ScaleX;
                    float y = frames[f + 2] * boneData.ScaleY;

                    float xOut = float.PositiveInfinity;
                    float yOut = float.PositiveInfinity;

                    px.outTangent = xOut;
                    py.outTangent = yOut;

                    xKeys.Add(new Keyframe(time, x, xOut, 0));
                    yKeys.Add(new Keyframe(time, y, yOut, 0));

                    xKeys[pIndex] = px;
                    yKeys[pIndex] = py;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 2)
                {
                    //bezier
                    Keyframe px = xKeys[pIndex];
                    Keyframe py = yKeys[pIndex];

                    float time = frames[f];

                    int steps = Mathf.FloorToInt((time - px.time) / BakeIncrement);

                    for (int i = 1; i <= steps; i++)
                    {
                        currentTime += BakeIncrement;
                        if (i == steps)
                            currentTime = time;

                        timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                        px = xKeys[listIndex - 1];
                        py = yKeys[listIndex - 1];

                        float xOut = (bone.ScaleX - px.value) / (currentTime - px.time);
                        float yOut = (bone.ScaleY - py.value) / (currentTime - py.time);

                        px.outTangent = xOut;
                        py.outTangent = yOut;

                        xKeys.Add(new Keyframe(currentTime, bone.ScaleX, xOut, 0));
                        yKeys.Add(new Keyframe(currentTime, bone.ScaleY, yOut, 0));

                        xKeys[listIndex - 1] = px;
                        yKeys[listIndex - 1] = py;

                        listIndex++;
                        lastTime = currentTime;
                    }
                }

                frameIndex++;
                f += 3;
            }

            xCurve = EnsureCurveKeyCount(new AnimationCurve(xKeys.ToArray()));
            yCurve = EnsureCurveKeyCount(new AnimationCurve(yKeys.ToArray()));

            string path = GetPath(boneData);
            string propertyName = "localScale";

            clip.SetCurve(path, typeof(Transform), propertyName + ".x", xCurve);
            clip.SetCurve(path, typeof(Transform), propertyName + ".y", yCurve);
            clip.SetCurve(path, typeof(Transform), propertyName + ".z", zCurve);
        }

        static void ParseRotateTimeline(Skeleton skeleton, RotateTimeline timeline, AnimationClip clip)
        {
            var boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
            var bone = skeleton.Bones.Items[timeline.BoneIndex];

            var curve = new AnimationCurve();

            float endTime = timeline.Frames[(timeline.FrameCount * 2) - 2];

            float currentTime = timeline.Frames[0];

            var keys = new List<Keyframe>();

            float rotation = timeline.Frames[1] + boneData.Rotation;

            keys.Add(new Keyframe(timeline.Frames[0], rotation, 0, 0));

            int listIndex = 1;
            int frameIndex = 1;
            int f = 2;
            float[] frames = timeline.Frames;
            skeleton.SetToSetupPose();
            float lastTime = 0;
            float angle = rotation;
            while (currentTime < endTime)
            {
                int pIndex = listIndex - 1;
                float curveType = timeline.GetCurveType(frameIndex - 1);

                if (curveType == 0)
                {
                    //linear
                    Keyframe pk = keys[pIndex];

                    float time = frames[f];

                    rotation = frames[f + 1] + boneData.Rotation;
                    angle += Mathf.DeltaAngle(angle, rotation);
                    float r = angle;

                    float rOut = (r - pk.value) / (time - pk.time);

                    pk.outTangent = rOut;

                    keys.Add(new Keyframe(time, r, rOut, 0));

                    keys[pIndex] = pk;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 1)
                {
                    //stepped

                    Keyframe pk = keys[pIndex];

                    float time = frames[f];

                    rotation = frames[f + 1] + boneData.Rotation;
                    angle += Mathf.DeltaAngle(angle, rotation);
                    float r = angle;

                    float rOut = float.PositiveInfinity;

                    pk.outTangent = rOut;

                    keys.Add(new Keyframe(time, r, rOut, 0));

                    keys[pIndex] = pk;

                    currentTime = time;

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);

                    lastTime = time;
                    listIndex++;
                }
                else if (curveType == 2)
                {
                    //bezier
                    Keyframe pk = keys[pIndex];

                    float time = frames[f];

                    timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
                    skeleton.UpdateWorldTransform();

                    rotation = frames[f + 1] + boneData.Rotation;
                    angle += Mathf.DeltaAngle(angle, rotation);
                    float r = angle;

                    int steps = Mathf.FloorToInt((time - pk.time) / BakeIncrement);

                    for (int i = 1; i <= steps; i++)
                    {
                        currentTime += BakeIncrement;
                        if (i == steps)
                            currentTime = time;

                        timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
                        skeleton.UpdateWorldTransform();
                        pk = keys[listIndex - 1];

                        rotation = bone.Rotation;
                        angle += Mathf.DeltaAngle(angle, rotation);
                        r = angle;

                        float rOut = (r - pk.value) / (currentTime - pk.time);

                        pk.outTangent = rOut;

                        keys.Add(new Keyframe(currentTime, r, rOut, 0));

                        keys[listIndex - 1] = pk;

                        listIndex++;
                        lastTime = currentTime;
                    }
                }

                frameIndex++;
                f += 2;
            }

            curve = EnsureCurveKeyCount(new AnimationCurve(keys.ToArray()));

            string path = GetPath(boneData);
            const string propertyName = "localEulerAnglesBaked";

            EditorCurveBinding xBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".x");
            AnimationUtility.SetEditorCurve(clip, xBind, new AnimationCurve());
            EditorCurveBinding yBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".y");
            AnimationUtility.SetEditorCurve(clip, yBind, new AnimationCurve());
            EditorCurveBinding zBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".z");
            AnimationUtility.SetEditorCurve(clip, zBind, curve);
        }

        static void ParseEventTimeline(EventTimeline timeline, AnimationClip clip, SendMessageOptions eventOptions)
        {
            float[] frames = timeline.Frames;
            var events = timeline.Events;

            var animEvents = new List<AnimationEvent>();
            for (int i = 0, n = frames.Length; i < n; i++)
            {
                var spineEvent = events[i];
                string eventName = spineEvent.Data.Name;
                if (SpineEditorUtilities.Preferences.mecanimEventIncludeFolderName)
                    eventName = eventName.Replace("/", ""); // calls method FolderNameEventName()
                else
                    eventName = eventName.Substring(eventName.LastIndexOf('/') + 1); // calls method EventName()
                var unityAnimationEvent = new AnimationEvent
                {
                    time = frames[i],
                    functionName = eventName,
                    messageOptions = eventOptions
                };

                if (!string.IsNullOrEmpty(spineEvent.String))
                {
                    unityAnimationEvent.stringParameter = spineEvent.String;
                }
                else if (spineEvent.Int != 0)
                {
                    unityAnimationEvent.intParameter = spineEvent.Int;
                }
                else if (spineEvent.Float != 0)
                {
                    unityAnimationEvent.floatParameter = spineEvent.Float;
                } // else, paramless function/Action.

                animEvents.Add(unityAnimationEvent);
            }

            AnimationUtility.SetAnimationEvents(clip, animEvents.ToArray());
        }

        static void ParseAttachmentTimeline(Skeleton skeleton, AttachmentTimeline timeline, Dictionary<int, List<string>> slotLookup, AnimationClip clip)
        {
            var attachmentNames = slotLookup[timeline.SlotIndex];

            string bonePath = GetPath(skeleton.Slots.Items[timeline.SlotIndex].Bone.Data);
            string slotPath = bonePath + "/" + skeleton.Slots.Items[timeline.SlotIndex].Data.Name;

            Dictionary<string, AnimationCurve> curveTable = new Dictionary<string, AnimationCurve>();

            foreach (string str in attachmentNames)
            {
                curveTable.Add(str, new AnimationCurve());
            }

            float[] frames = timeline.Frames;

            if (frames[0] != 0)
            {
                string startingName = skeleton.Slots.Items[timeline.SlotIndex].Data.AttachmentName;
                foreach (var pair in curveTable)
                {
                    if (startingName == "" || startingName == null)
                    {
                        pair.Value.AddKey(new Keyframe(0, 0, float.PositiveInfinity, float.PositiveInfinity));
                    }
                    else
                    {
                        if (pair.Key == startingName)
                        {
                            pair.Value.AddKey(new Keyframe(0, 1, float.PositiveInfinity, float.PositiveInfinity));
                        }
                        else
                        {
                            pair.Value.AddKey(new Keyframe(0, 0, float.PositiveInfinity, float.PositiveInfinity));
                        }
                    }
                }
            }

            float currentTime = timeline.Frames[0];
            float endTime = frames[frames.Length - 1];
            int f = 0;

            do
            {
                float time = frames[f];

                int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : BinarySearch(frames, time)) - 1;

                string name = timeline.AttachmentNames[frameIndex]?.Replace("/", "^");

                foreach (var pair in curveTable)
                {
                    if (name == "" || name == null)
                    {
                        pair.Value.AddKey(new Keyframe(time, 0, float.PositiveInfinity, float.PositiveInfinity));
                    }
                    else
                    {
                        if (pair.Key == name)
                        {
                            pair.Value.AddKey(new Keyframe(time, 1, float.PositiveInfinity, float.PositiveInfinity));
                        }
                        else
                        {
                            pair.Value.AddKey(new Keyframe(time, 0, float.PositiveInfinity, float.PositiveInfinity));
                        }
                    }
                }

                currentTime = time;
                f += 1;
            }
            while (currentTime < endTime);


            foreach (var pair in curveTable)
            {
                string path = slotPath + "/" + pair.Key;
                string prop = "m_IsActive";

                clip.SetCurve(path, typeof(GameObject), prop, pair.Value);
            }
        }
        static void ParseShearTimeline(ShearTimeline timeline, AnimationClip clip)
        {
            Debug.Log("has ShearTimeline");
        }
        static void ParseDrawOrderTimeline(DrawOrderTimeline timeline, AnimationClip clip)
        {
            Debug.Log("has DrawOrderTimeline");
        }
        static void ParseDeformTimeline(Skeleton skeleton, DeformTimeline timeline, ref Dictionary<string, List<(Vector2, List<Vector3>)>> data, AnimationClip clip)
        {
            var bone = skeleton.Slots.Items[timeline.SlotIndex];
            string bonePath = GetPath(bone.Bone.Data);
            string slotPath = bonePath + "/" + bone.Data.Name;
            List<(Vector2, List<Vector3>)> timeline_data = new List<(Vector2, List<Vector3>)>();
            var slot = skeleton.Slots.Items[timeline.SlotIndex];
            float[] save = timeline.Attachment.Vertices;


            for (int n = 0, m = timeline.FrameCount; n < m; n++)
            {
                Vector2 time_and_ctype = new Vector2(timeline.Frames[n], timeline.GetCurveType(n));
                float[] v_pos_c = new float[timeline.Attachment.WorldVerticesLength];
                float[] v_set = new float[save.Length];
                if (timeline.Attachment.Bones == null)
                    v_set = timeline.Vertices[n];
                else
                    for (int i = 0; i < save.Length; i += 3)
                    {
                        v_set[i] = save[i] + timeline.Vertices[n][i / 3 * 2];
                        v_set[i + 1] = save[i + 1] + timeline.Vertices[n][i / 3 * 2 + 1];
                        v_set[i + 2] = save[i + 2];
                    }
                timeline.Attachment.Vertices = v_set;
                timeline.Attachment.ComputeWorldVertices(slot, v_pos_c);

                List<Vector3> v_pos = ExtractVerts(v_pos_c).ToList();
                timeline_data.Add((time_and_ctype, v_pos));
            }


            string path = slotPath + "/" + timeline.Attachment.Name.Replace("/", "^");
            data[path] = timeline_data;
            timeline.Attachment.Vertices = save;
        }
        static void ParseTwoColorTimeline(TwoColorTimeline timeline, AnimationClip clip)
        {
            Debug.Log("has TwoColorTimeline");
        }
        static void ParseColorTimeline(Skeleton skeleton, ColorTimeline timeline, Dictionary<int, List<string>> slotLookup, AnimationClip clip)
        {
            float[] frames = timeline.Frames;
            const string color = "material._Color";

            var attachmentNames = slotLookup[timeline.SlotIndex];

            var data = skeleton.Slots.Items[timeline.SlotIndex];
            string bonePath = GetPath(data.Bone.Data);
            string slotPath = bonePath + "/" + data.Data.Name;


            AnimationCurve[] rgbaCurve = new AnimationCurve[] { new AnimationCurve(), new AnimationCurve(), new AnimationCurve(), new AnimationCurve() };
            if (frames[0] != 0)
            {
                float[] pre_color = new float[4] { data.R, data.G, data.B, data.A };
                for (int p = 0; p < rgbaCurve.Length; p++)
                {
                    Keyframe[] rgbaKeys = new Keyframe[4];
                    rgbaKeys[p] = new Keyframe(0, pre_color[p], float.PositiveInfinity, float.PositiveInfinity);
                    rgbaCurve[p].AddKey(rgbaKeys[p]);
                    AnimationUtility.SetKeyBroken(rgbaCurve[p], rgbaCurve[p].length - 1, true);
                }
            }
            float[] rgbaTangent = new float[4] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity };

            for (int i = 0; i < frames.Length; i += 5)
            {

                for (int p = 0; p < rgbaCurve.Length; p++)
                {
                    Keyframe[] rgbaKeys = new Keyframe[4];

                    switch (timeline.GetCurveType(i / 5))
                    {
                        case 1:
                            rgbaKeys[p] = new Keyframe(frames[i], frames[i + 1 + p], rgbaTangent[p], float.PositiveInfinity);
                            break;
                        default:
                            float outTangent = i + 5 < frames.Length ? (frames[i + 6 + p] - frames[i + 1 + p]) / (frames[i + 5] - frames[i]) : 0;
                            rgbaKeys[p] = new Keyframe(frames[i], frames[i + 1 + p], rgbaTangent[p], outTangent);
                            break;
                    }
                    rgbaTangent[p] = rgbaKeys[p].outTangent;
                    rgbaCurve[p].AddKey(rgbaKeys[p]);
                    AnimationUtility.SetKeyBroken(rgbaCurve[p], rgbaCurve[p].length - 1, true);
                }



                foreach (var item in attachmentNames)
                {
                    string path = slotPath + "/" + item;
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".r", rgbaCurve[0]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".g", rgbaCurve[1]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".b", rgbaCurve[2]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".a", rgbaCurve[3]);
                }

            }
        }



        static AnimationCurve EnsureCurveKeyCount(AnimationCurve curve)
        {

            return curve;
        }

        static float GetUninheritedAppliedRotation(Bone b)
        {
            Bone parent = b.Parent;
            float angle = b.AppliedRotation;

            while (parent != null)
            {
                angle -= parent.AppliedRotation;
                parent = parent.Parent;
            }

            return angle;
        }
        #endregion
        #endregion

        #region Region Baking
        public static GameObject BakeRegion(SpineAtlasAsset atlasAsset, AtlasRegion region, bool autoSave = true)
        {
            atlasAsset.GetAtlas(); // Initializes atlasAsset.

            string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
            string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath).Replace('\\', '/');
            string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
            string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeName(region.name) + ".prefab").Replace("\\", "/");

            GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
            GameObject root;
            Mesh mesh;
            bool isNewPrefab = false;

            if (!Directory.Exists(bakedDirPath))
                Directory.CreateDirectory(bakedDirPath);

            if (prefab == null)
            {
                root = EditorInstantiation.NewGameObject("temp", true, typeof(MeshFilter), typeof(MeshRenderer));
#if NEW_PREFAB_SYSTEM
                prefab = PrefabUtility.SaveAsPrefabAsset(root, bakedPrefabPath);
#else
				prefab = PrefabUtility.CreatePrefab(bakedPrefabPath, root);
#endif

                isNewPrefab = true;
                Object.DestroyImmediate(root);
            }

            mesh = (Mesh)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(Mesh));

            Material mat = null;
            mesh = atlasAsset.GenerateMesh(region.name, mesh, out mat);
            if (isNewPrefab)
            {
                AssetDatabase.AddObjectToAsset(mesh, prefab);
                prefab.GetComponent<MeshFilter>().sharedMesh = mesh;
            }

            EditorUtility.SetDirty(mesh);
            EditorUtility.SetDirty(prefab);

            if (autoSave)
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }

            prefab.GetComponent<MeshRenderer>().sharedMaterial = mat;

            return prefab;
        }
        #endregion

        static string GetPath(BoneData b)
        {
            return GetPathRecurse(b).Substring(1);
        }

        static string GetPathRecurse(BoneData b)
        {
            if (b == null) return "";
            return GetPathRecurse(b.Parent) + "/" + b.Name;
        }

        static void SetAnimationSettings(AnimationClip clip, AnimationClipSettings settings)
        {
            AnimationUtility.SetAnimationClipSettings(clip, settings);
        }
        private static void setSetupPoseClip(ref AnimatorController controller, List<Transform> boneslist, Dictionary<string, Transform> slottable, List<SlotData> slotdatas)
        {
            ChildAnimatorState state = controller.layers[0].stateMachine.states.FirstOrDefault((clip) => { if (clip.state.name == "SetupPose") { return clip.state; } else return false; });
            if (state.state != null && state.state.motion != null) return;


            AnimationClip clip = new AnimationClip();
            clip.name = "SetupPose";
            clip.ClearCurves();
            const string localPosition = "localPosition";
            const string localEulerAnglesBaked = "localEulerAnglesBaked";
            const string localScale = "localScale";
            foreach (var item in boneslist)
            {
                string path = findPath(item);

                Keyframe[] xKeys = new Keyframe[1] { new Keyframe(0, item.localPosition.x) };
                Keyframe[] yKeys = new Keyframe[1] { new Keyframe(0, item.localPosition.y) };
                Keyframe[] zKeys = new Keyframe[1] { new Keyframe(0, item.localEulerAngles.z) };
                Keyframe[] xsKeys = new Keyframe[1] { new Keyframe(0, item.localScale.x) };
                Keyframe[] ysKeys = new Keyframe[1] { new Keyframe(0, item.localScale.y) };

                AnimationCurve xCurve = new AnimationCurve(xKeys);
                AnimationCurve yCurve = new AnimationCurve(yKeys);
                AnimationCurve zCurve = new AnimationCurve(zKeys);
                AnimationCurve xsCurve = new AnimationCurve(xsKeys);
                AnimationCurve ysCurve = new AnimationCurve(ysKeys);

                clip.SetCurve(path, typeof(Transform), localScale + ".x", xsCurve);
                clip.SetCurve(path, typeof(Transform), localScale + ".y", ysCurve);

                clip.SetCurve(path, typeof(Transform), localPosition + ".x", xCurve);
                clip.SetCurve(path, typeof(Transform), localPosition + ".y", yCurve);
                clip.SetCurve(path, typeof(Transform), localPosition + ".z", new AnimationCurve());

                EditorCurveBinding xBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), localEulerAnglesBaked + ".x");
                AnimationUtility.SetEditorCurve(clip, xBind, new AnimationCurve());
                EditorCurveBinding yBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), localEulerAnglesBaked + ".y");
                AnimationUtility.SetEditorCurve(clip, yBind, new AnimationCurve());
                EditorCurveBinding zBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), localEulerAnglesBaked + ".z");
                AnimationUtility.SetEditorCurve(clip, zBind, zCurve);
            }
            const string color = "material._Color";
            const string active = "m_IsActive";
            foreach (var item in slotdatas)
            {


                Keyframe[] rgbaKeys = new Keyframe[4] { new Keyframe(0, item.R), new Keyframe(0, item.G), new Keyframe(0, item.B), new Keyframe(0, item.A) };
                AnimationCurve[] rgbaCurve = new AnimationCurve[] { new AnimationCurve(rgbaKeys[0]), new AnimationCurve(rgbaKeys[1]), new AnimationCurve(rgbaKeys[2]), new AnimationCurve(rgbaKeys[3]) };
                foreach (var item1 in slottable[item.Name].GetComponentsInChildren<SkinnedMeshRenderer>(true))
                {
                    AnimationCurve atCurve = new AnimationCurve(new Keyframe(0, item1.gameObject.activeSelf ? 1f : 0f));

                    string path = findPath(item1.transform);
                    clip.SetCurve(path, typeof(GameObject), active, atCurve);


                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".r", rgbaCurve[0]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".g", rgbaCurve[1]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".b", rgbaCurve[2]);
                    clip.SetCurve(path, typeof(SkinnedMeshRenderer), color + ".a", rgbaCurve[3]);
                }

            }
            if (state.state == null)
                controller.AddMotion(clip);
            else
                state.state.motion = clip;
            AssetDatabase.AddObjectToAsset(clip, controller);

        }
        private static string findPath(Transform tra)
        {
            if (tra.name != "root")
                return findPath(tra.parent) + "/" + tra.name;
            else
                return tra.name;
        }

    }

}
